
#include <new>
#include <apr_pools.h>
#include <apr_strings.h>
#include "../iface/debug.h"
#include "../iface/testerror.h"
#include "../iface/APRInterface.h"
#include "../utils/StringUtils.h"
#include "../net/netgeneral.h"
#include "EncMessage.h"

/** Constructor for the encapsulated message.
 * 
 * @param imp The parent memory pool. */
EncMessage::EncMessage(apr_pool_t *imp)
{
   highmp = imp;

   apr_status_t status;
   status = apr_pool_create(&mp, highmp);
   CheckAPRError(status);

   recipient = NULL;
   sender = NULL;
   htable = NULL;
   messagetype = NULL;
   processee = NULL;

   headers = NULL;
   payloads = NULL;
   lengths = NULL;

   // Sign messages by default
   willsign = 1;
   // Is signed - so, has been signed and verified
   issigned = 0;
   // Remove sending key - dangerous!
   removesendingkey = 0;
}

/** Copy constructor for the encapsulated message class. */
EncMessage::EncMessage(const EncMessage &other)
{
   copy(other);
}

/** Copy assignment operator for the encapsulated message class. */
EncMessage &EncMessage::operator=(const EncMessage &other)
{
   copy(other);
   return *this;
}

/** Destructor for the encapsulated message. */
EncMessage::~EncMessage()
{
   if (payloads)
   {
      for (int k = 0; k < payloads->size(); k++)
      {
         int slen = strlen(payloads->at(k));
         memset((void *)payloads->at(k), 0, sizeof(char)*slen);
      }
   }
   if (payloads) payloads->~vector<const char *>();
   payloads = NULL;
   if (lengths) lengths->~vector<unsigned int>();
   lengths = NULL;
   if (headers) headers->~vector<const char *>();
   headers = NULL;
   if (htable) delete htable;
   htable = NULL;
   apr_pool_destroy(mp);
}

/** Private copy constructor for the encapsulated message
 * 
 * @param other The other message to copy. */
void EncMessage::copy(const EncMessage &other)
{
   highmp = other.highmp;
   
   apr_status_t status;
   status = apr_pool_create(&mp, highmp);
   CheckAPRError(status);

   recipient = (other.recipient) ? apr_pstrdup(mp, other.recipient) : NULL;
   recipientkey = recipient;
   sender = (other.sender) ? apr_pstrdup(mp, other.sender) : NULL;
   senderkey = sender;
   htable = (other.htable) ? new APRTable(*(other.htable)) : NULL;
   messagetype = (other.messagetype) ? apr_pstrdup(mp, other.messagetype) : NULL;
   processee = (other.processee) ? apr_pstrdup(mp, other.processee) : NULL;

   if (other.payloads != NULL)
   {
      void *payloads_mem = (std::vector<const char *> *)apr_palloc(mp, sizeof(std::vector<const char *>));
      void *lengths_mem = (std::vector<unsigned int> *)apr_palloc(mp, sizeof(std::vector<unsigned int>));
      payloads = new(payloads_mem) std::vector<const char *>();
      lengths = new(lengths_mem) std::vector<unsigned int>();
      for (int i = 0; i < lengths->size(); i++)
      {
         unsigned int tlength = (other.lengths)->at(i);
         const char *tpayload = (other.payloads)->at(i);
         lengths->push_back((other.lengths)->at(i));
         payloads->push_back((const char *)apr_pmemdup(mp, tpayload, tlength));
      }
   }
   if (other.headers != NULL)
   {
      void *headers_mem = (std::vector<const char *> *)apr_palloc(mp, sizeof(std::vector<const char *>));
      headers = new(headers_mem) std::vector<const char *>();
      for (int i = 0; i < headers->size(); i++)
      {
         const char *theader = (other.headers)->at(i);
         headers->push_back(apr_pstrdup(mp, theader));
      }
   }
   
   willsign = other.willsign;
   // Is signed - so, has been signed and verified
   issigned = other.issigned;
   // Remove sending key - dangerous!
   removesendingkey = other.removesendingkey;
}

/** A quick reply function to generate the new sender and recipient
 * from the old message.
 * 
 * @param imsg The original message that is to be replied to. */
void EncMessage::replyTo(const EncMessage &imsg)
{
   setRecipient(imsg.getSender());
   setSender(imsg.getRecipient());
}

/** Sets the recipient of the message with a format string
 * 
 * @param iformat The format string. */
void EncMessage::setRecipient(const char *iformat, ...)
{
   FATAL_IF_NULL((void)0, iformat, "recipient");
   va_list args; va_list newcopy;
   va_start(args, iformat); va_copy(newcopy, args);
   recipient = StringUtils::mpprintf_va(mp, iformat, newcopy);
   va_end(newcopy); va_end(args);
   StringUtils::aggressiveStringSanitiser((char *)recipient);
   recipientkey = recipient;
}

/** Sets the sender of the message with a format string
 * 
 * @param iformat The format string. */
void EncMessage::setSender(const char *iformat, ...)
{
   FATAL_IF_NULL((void)0, iformat, "sender");
   va_list args; va_list newcopy;
   va_start(args, iformat); va_copy(newcopy, args);
   sender = StringUtils::mpprintf_va(mp, iformat, newcopy);
   va_end(newcopy); va_end(args);
   StringUtils::aggressiveStringSanitiser((char *)sender);
   senderkey = sender;
}

/** Sets the message type and processee (actor to be worked on)
 * header.
 * 
 * @param itype A string representing the intention of the message.
 * 
 * @param iprocessee A string representing the actor to be acted
 * upon. */
void EncMessage::addTypeHeaderNOCRLF(const char *itype, const char *iprocessee)
{
   FATAL_IF_NULL((void)0, itype, "message type");
   FATAL_IF_NULL((void)0, iprocessee, "message processee");
   if (headers)
   {
      errprint("headers already specified");
      return;
   }
   messagetype = apr_pstrdup(mp, itype);
   processee = apr_pstrdup(mp, iprocessee);
   StringUtils::aggressiveStringSanitiser((char *)processee);
   void *headers_mem = (void *)apr_palloc(mp, sizeof(std::vector<const char *>));
   headers = new(headers_mem) std::vector<const char *>();
   headers->push_back(apr_pstrcat(mp, itype, " ", iprocessee, NULL));
}

/** Add a header line described by the format string given, of the
 * form 'Variable: Key'. This is without the CR/LF header combination
 * that is appended when this is sent in the final message.
 * 
 * @param iformat The format string. */
void EncMessage::addHeaderLineNOCRLF(const char *iformat, ...)
{
   FATAL_IF_NULL((void)0, iformat, "header line");
   if (!headers)
   {
      errprint("must specify message type specific header first");
      return;
   }
   va_list args; va_list newcopy;
   va_start(args, iformat); va_copy(newcopy, args);
   headers->push_back(StringUtils::mpprintf_va(mp, iformat, newcopy));
   va_end(newcopy); va_end(args);
}

/** Add a format string as a payload to the message.
 * 
 * @param iformat The format string.
 * 
 * @return The payload number. */
int EncMessage::addPayloadString(const char *iformat, ...)
{
   FATAL_IF_BLANK(-1, iformat, "payload string");
   if (!payloads)
   {
      void *payloads_mem = (std::vector<const char *> *)apr_palloc(mp, sizeof(std::vector<const char *>));
      void *lengths_mem = (std::vector<unsigned int> *)apr_palloc(mp, sizeof(std::vector<unsigned int>));
      payloads = new(payloads_mem) std::vector<const char *>();
      lengths = new(lengths_mem) std::vector<unsigned int>();
   }
   va_list args; va_list newcopy;
   va_start(args, iformat); va_copy(newcopy, args);
   const char *newline = StringUtils::mpprintf_va(mp, iformat, newcopy);
   va_end(newcopy); va_end(args);
   payloads->push_back(newline);
   lengths->push_back(strlen(newline));
   return payloads->size() - 1;
}

/** Add a string verbatim as a payload to the message.
 * 
 * @param istring The string to be copied to the payload verbatim.
 * 
 * @return The payload number. */
int EncMessage::addVerbatimPayloadString(const char *istring)
{
   FATAL_IF_BLANK(-1, istring, "payload string");
   if (!payloads)
   {
      void *payloads_mem = (std::vector<const char *> *)apr_palloc(mp, sizeof(std::vector<const char *>));
      void *lengths_mem = (std::vector<unsigned int> *)apr_palloc(mp, sizeof(std::vector<unsigned int>));
      payloads = new(payloads_mem) std::vector<const char *>();
      lengths = new(lengths_mem) std::vector<unsigned int>();
   }
   const char *newline = apr_pstrdup(mp, istring);
   payloads->push_back(newline);
   lengths->push_back(strlen(newline));
   return payloads->size() - 1;
}

/** Add a buffer as a payload to the message.
 * 
 * @param ibuffer The buffer to add to the message.
 * 
 * @param ilength The length of the buffer that should be added.
 * 
 * @return The payload number. */
int EncMessage::addPayloadBuffer(const char *ibuffer, int ilength)
{
   FATAL_IF_NULL(-1, ibuffer, "payload buffer");
   if (!payloads)
   {
      void *payloads_mem = (std::vector<const char *> *)apr_palloc(mp, sizeof(std::vector<const char *>));
      void *lengths_mem = (std::vector<unsigned int> *)apr_palloc(mp, sizeof(std::vector<unsigned int>));
      payloads = new(payloads_mem) std::vector<const char *>();
      lengths = new(lengths_mem) std::vector<unsigned int>();
   }
   void *memblock = (void *)apr_palloc(mp, ilength+1);
   memcpy(memblock, ibuffer, ilength);
   ((char *)memblock)[ilength] = '\0';
   payloads->push_back((const char *)memblock);
   lengths->push_back(ilength);
   return payloads->size() - 1;
}

/** Add a text file as a payload to the message, expanding the given
 * format string to be the filename.
 * 
 * @param iformat The format string to expand to the text file name to
 * add.
 * 
 * @return The payload number. */
int EncMessage::addPayloadTextFile(const char *iformat, ...)
{
   FATAL_IF_BLANK(-1, iformat, "format string for filename");
   va_list args; va_list newcopy;
   va_start(args, iformat); va_copy(newcopy, args);
   const char *newline = StringUtils::mpprintf_va(mp, iformat, newcopy);
   va_end(newcopy); va_end(args);
   // Should probably add binary support to this!
   char *string = NULL;
   StringUtils::readFileToString(&string, newline, mp);
   int retval = addVerbatimPayloadString(string);
   free(string);
   return retval;
}

/** Force no signing to be performed on the message when it is
 * sent. */
void EncMessage::forceNoSignature()
{
   willsign = 0;
   sender = StringUtils::mpprintf(mp, ".%s", senderkey);
}

/** Force the sending key to be expunged upon sending. */
void EncMessage::forceDestroySendingKey()
{
   removesendingkey = 1;
   senderkey = StringUtils::mpprintf(mp, ".%s", sender);
}

/** Reset the message to a blank state. */
void EncMessage::reset()
{
   recipient = NULL;
   sender = NULL;
   htable = NULL;
   messagetype = NULL;
   processee = NULL;

   if (payloads) payloads->~vector<const char *>();
   payloads = NULL;
   if (lengths) lengths->~vector<unsigned int>();
   lengths = NULL;
   if (headers) headers->~vector<const char *>();
   headers = NULL;

   // Sign messages by default
   willsign = 1;
   // Is signed - so, has been signed and verified (by default no)
   issigned = 0;

   apr_pool_destroy(mp);

   apr_status_t status;
   status = apr_pool_create(&mp, highmp);
   CheckAPRError(status);
}

/** Set a flag to say that the message was signed and verified to be
 * genuine. */
void EncMessage::setWasSignedFlag()
{
   issigned = 1;
}

/** Generate the APR table of headers of the message.
 * 
 * @param stream Parse the stream into headers. */
void EncMessage::parseHeaderStream(const char *stream)
{
   if (stream == NULL) return;
   if (headers)
   {
      errprint("headers already exist, cannot parse headers into this instance");
      return;
   }
   const char *firstheader_idxptr = strstr(stream, CRLF_STR);
   int offset = 0;
   if (firstheader_idxptr != NULL)
   {
      offset = firstheader_idxptr - stream;
   } else
   {
      errprint("could not find header line end in stream");
   }
   char *firstheader_dup = (char *)apr_palloc(mp, sizeof(char)*(offset + 1));
   memcpy(firstheader_dup, stream, sizeof(char)*offset);
   firstheader_dup[offset] = '\0';
   char *remainder = NULL;
   messagetype = apr_strtok(firstheader_dup, " ", &remainder);
   processee = apr_strtok(NULL, " ", &remainder);
   char *dup_stream = apr_pstrdup(mp, stream);
   void *headers_mem = (void *)apr_palloc(mp, sizeof(std::vector<const char *>));
   headers = new(headers_mem) std::vector<const char *>();
   remainder = NULL;
   char *ttoken = apr_strtok(dup_stream, CRLF_STR, &remainder);
   while ((ttoken != NULL) && (remainder != NULL))
   {
      if (ttoken[0] >= ' ') headers->push_back(ttoken);
      ttoken = apr_strtok(NULL, CRLF_STR, &remainder);
   }
   generateHeaderTable();
}

/** Generate a table of headers in a APR table. */
void EncMessage::generateHeaderTable()
{
   if (!headers)
   {
      errprint("no header table");
      return;
   }
   if (headers->size() == 0)
   {
      errprint("no header in the string list");
      return;
   }
   if (htable)
   {
      errprint("header table already exists");
      return;
   }
   htable = new APRTable(headers->size(), mp);
   for (int k = 0; k < headers->size(); k++)
   {
      char *remainder = NULL;
      char *dupstr = (char *)apr_pstrdup(mp, headers->at(k));
      const char *key_token = dupstr;
      char *begin_between_block = strstr(dupstr, ": ");
      if (begin_between_block == NULL)
         begin_between_block = strstr(dupstr, " ");
      begin_between_block[0] = '\0';
      const char *value_token = &(begin_between_block[2]);
      // Fairly important change here!
      //const char *key_token = apr_strtok(dupstr, ": ", &remainder);
      //const char *value_token = apr_strtok(NULL, ": " CRLF_STR, &remainder);
      if (value_token == NULL)
         continue;
      while ((value_token[0] == ' ') || (value_token[0] == ':'))
         value_token = &(value_token[1]);
         //value_token = apr_strtok(NULL, ": " CRLF_STR, &remainder);
      htable->set(key_token, value_token);
   }
}

/** Get a copy of the stream used to construct the header fields.
 * 
 * @return A copy of the stream. This is allocated with the memory
 * pool, so it does not need to be freed. */
char *EncMessage::getHeaderStream() const
{
   if (!headers)
   {
      errprint("no header table");
      return NULL;
   }
   if (headers->size() == 0)
   {
      errprint("no header in the string list");
      return NULL;
   }
   // Prepare for the extra CRLFs and the NULL terminator
   int total_alloc = strlen(CRLF_STR)*headers->size() + 1;
   // Add all of the individual strlens
   for (int k = 0; k < headers->size(); k++)
      total_alloc += strlen(headers->at(k));
   int idx_position = 0;
   char *bigstream = (char *)apr_palloc(mp, sizeof(char)*total_alloc);
   bigstream[0] = '\0';
   for (int k = 0; k < headers->size(); k++)
   {
      strcat(bigstream, headers->at(k));
      strcat(bigstream, CRLF_STR);
   }
   return bigstream;
}

/** Get the recipient of this message.
 * 
 * @return The recipient name as a string. This is allocated from the
 * memory pool so doesn't need to be freed. */
const char *EncMessage::getRecipient() const
{
   return recipient;
}

/** Get the sender of this message.
 * 
 * @return The sender name as a string. This is allocated from the
 * memory pool so doesn't need to be freed. */
const char *EncMessage::getSender() const
{
   return sender;
}

/** Get the type of this message.
 * 
 * @return The type name as a string. This is allocated from the
 * memory pool so doesn't need to be freed. */
const char *EncMessage::getType() const
{
   return messagetype;
}

/** Get the processee of this message.
 * 
 * @return The processee name as a string. This is allocated from the
 * memory pool so doesn't need to be freed. */
const char *EncMessage::getProcessee() const
{
   return processee;
}

/** Get the number of payloads in this message.
 * 
 * @return The number of payloads in the message. */
const int EncMessage::getHowManyPayloads() const
{
   return (payloads) ? payloads->size() : 0;
}

/** Get the pointer to the payload number given.
 * 
 * @param pnum The payload number to get the pointer to.
 * 
 * @return The pointer to the payload number given. */
const char *EncMessage::getPayload(int pnum) const
{
   if ((pnum < 0) || (pnum >= payloads->size())) return NULL;
   return payloads->at(pnum);
}

/** Get the length in bytes of the payload number given.
 * 
 * @param pnum The payload number to get the length of.
 * 
 * @return The number of bytes in the given payload number. */
const int EncMessage::getPayloadLength(int pnum) const
{
   if ((pnum < 0) || (pnum >= lengths->size())) return 0;
   return lengths->at(pnum);
}

/** Does the type of this message match the given type?
 * 
 * @param ctype The type of this message as a string.
 * 
 * @return True (non-zero) if it matches, false (zero) otherwise. */
const int EncMessage::isType(const char *ctype) const
{
   if (!ctype) return 0;
   if (!messagetype) return 0;
   return (strcmp(ctype, messagetype) == 0);
}

/** Does the processee of this message match the given processee?
 * 
 * @param cprocessee The processee of this message as a string.
 * 
 * @return True (non-zero) if it matches, false (zero) otherwise. */
const int EncMessage::isProcessee(const char *cprocessee) const
{
   if (!cprocessee) return 0;
   if (!processee) return 0;
   return (strcmp(cprocessee, processee) == 0);
}

/** Does the payload count match the given payload count?
 * 
 * @param pcount The suggested payload count of this message.
 * 
 * @return True (non-zero) if it matches, false (zero) otherwise. */
const int EncMessage::isPayloadCount(const int pcount) const
{
   return (pcount == getHowManyPayloads());
}

/** Is this message guaranteed authentic by the signing and signature
 * checking process?
 * 
 * @return True (non-zero) if it matches, false (zero) otherwise. */
const int EncMessage::guaranteedAuthentic() const
{
   return issigned;
}

/** Are we signing this message before sending it?
 * 
 * @return True (non-zero is yes) if we are, false (zero is no)
 * otherwise. */
const int EncMessage::mustWeSign() const
{
   return willsign;
}

/** Returns the memory pool associated with this object.
 * 
 * @return The memory pool. */
apr_pool_t *EncMessage::getEncMessageAssociatedMemoryPool() const
{
   return mp;
}

/** Get a constant reference to the header table. This is freed along
 * with it's parent object.
 * 
 * @return The constant pointer to the APR table. */
const APRTable *EncMessage::getHeaderTable() const
{
   return htable;
}

APRTable *EncMessage::getAuxHeaderTable(int payload, const char *linesep, const char *keyvalsep, int *auxheaders) const
{
   char *dup_stream = apr_pstrdup(mp, getPayload(payload));
   char *combi_sep = apr_pstrcat(mp, keyvalsep, linesep, NULL);
   void *aux_headers_mem = (void *)apr_palloc(mp, sizeof(std::vector<const char *>));
   std::vector<const char *> *aux_headers = new std::vector<const char *>();
   char *remainder = NULL;
   char *ttoken = apr_strtok(dup_stream, linesep, &remainder);
   while (ttoken != NULL)
   {
      if (ttoken[0] >= ' ') aux_headers->push_back(ttoken);
      ttoken = apr_strtok(NULL, linesep, &remainder);
   }
   APRTable *aux_htable = new APRTable(aux_headers->size(), NULL);
   if (auxheaders) *auxheaders = aux_headers->size();
   for (int k = 0; k < aux_headers->size(); k++)
   {
      char *remainder = NULL;
      char *dupstr = (char *)apr_pstrdup(mp, aux_headers->at(k));
      const char *key_token = apr_strtok(dupstr, keyvalsep, &remainder);
      const char *value_token = apr_strtok(NULL, combi_sep, &remainder);
      char vtok[2];
      vtok[0] = value_token[0]; vtok[1] = '\0';
      while (strstr(keyvalsep, vtok) != NULL)
      {
         value_token = apr_strtok(NULL, combi_sep, &remainder);
         vtok[0] = value_token[0];
      }
      aux_htable->set(key_token, value_token);
   }
   delete aux_headers;
   return aux_htable;
}

/** Whether we are to remove the sending key afterwards.
 * 
 * @return True if we are to destroy the key, false if we do not. */
const int EncMessage::mustWeDestroySendingKey() const
{
   return removesendingkey;
}

/** Print the entire message. */
void EncMessage::print() const
{
   printf("From: %s\n", sender);
   printf("To: %s\n", recipient);
   printf("Type: %s\n", messagetype);
   printf("Subject: %s\n", processee);
   if ((headers) && (headers->size() > 1))
   {
      printf("Other headers:\n");
      for (int i = 1; i < headers->size(); i++)
         printf("%s\n", headers->at(i));
   }
   if ((payloads) && (payloads->size() > 0))
   {
      for (int i = 0; i < payloads->size(); i++)
      {
         printf("Payload %d:\n", i);
         if (StringUtils::isBinary(payloads->at(i), lengths->at(i)))
            printf("<binary data>\n");//PrintHex(payloads->at(i), lengths->at(i));
         else
            printf("%s\n", payloads->at(i));
      }
   }
}
